﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Web;
using System.Web.Caching;
using Q.Framework.Data;
using Q.Framework.Utility;
using Q.Framework.Extension;
using System.Data.EntityClient;
using System.Data.SqlClient;

namespace Q.Framework.WebCache
{
    public static class CacheManager
    {
        public static Boolean Initialized { get; set; }

        private static Dictionary<String, String> CacheableTypes
        {
            get
            {
                if (HttpContext.Current != null)
                {
                    if (HttpContext.Current.Application["CacheableTypes"] == null)
                        HttpContext.Current.Application.Add("CacheableTypes", new Dictionary<String, String>());
                    return (HttpContext.Current.Application["CacheableTypes"] as Dictionary<String, String>);
                }
                return new Dictionary<string,string>();
            }
            set
            {
                HttpContext.Current.Application["CacheableTypes"] = value;
            }
        }

        public static String ConnectionString { get; set; }

        public static Boolean CacheExists<T>()
        {
            String table = new Data.DAO<T>().GetEntitySetName();
            return CacheExists(table);
        }

        private static Boolean CacheExists(String tableName)
        {
            return CacheableTypes.ContainsKey(tableName);
        }

        private static void AddCache(Type type)
        {
            try
            {
                var conn = new SqlConnection(ConnectionString);
                conn.Open();

                Type DaoType = typeof(Data.DAO<>).MakeGenericType(type);
                object dao = Activator.CreateInstance(DaoType);

                String table = DaoType.GetMethod("GetEntitySetName").Invoke(dao, null) as String;
                if (!CacheExists(table))
                    CacheableTypes.Add(table, type.FullName);

                SqlCacheDependencyAdmin.EnableNotifications(ConnectionString);
                SqlCacheDependencyAdmin.EnableTableForNotifications(ConnectionString, table);

                SqlCacheDependency dep = new SqlCacheDependency(conn.Database, table);

                HttpContext.Current.Cache.Add(table, DaoType.GetMethod("SelectAll", new Type[] { typeof(Boolean) }).Invoke(dao, new object[] { true }), dep, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Normal, new CacheItemRemovedCallback(OnCacheRemoved));
                Initialized = true;
                conn.Close();
            }
            catch (Exception)
            {
                Initialized = false;
                throw;
            }
        }

        public static void AddCache<T>()
        {
            AddCache(typeof(T));
        }

        private static void OnCacheRemoved(String key, Object obj, CacheItemRemovedReason reason)
        {
            if (reason != CacheItemRemovedReason.Removed && HttpContext.Current != null)
                AddCache(Type.GetType(CacheableTypes[key]));
        }

        public static List<T> SelectAll<T>()
        {
            String table = new Data.DAO<T>().GetEntitySetName();
            if (HttpContext.Current.Cache[new Data.DAO<T>().GetEntitySetName()] == null)
                AddCache<T>();

            return HttpContext.Current.Cache[table] as List<T>;
        }

        public static T SelectOne<T>(Expression<Func<T, Boolean>> where)
        {
            var func = where.Compile();
            return SelectAll<T>().FirstOrDefault(func);
        }

        public static T GetById<T>(Int32 ID)
        {
            return SelectAll<T>().FirstOrDefault(a => (Int32)a.GetType().GetProperty("ID").GetValue(a, null) == ID);
        }

        public static PagedList<T> GetPagedList<T>(String order, int pageIndex, int pageSize)
        {
            order = order ?? "ID";
            if (!order.Contains("it."))
                order = "it." + order;
            List<T> lista = SelectAll<T>().OrderBy(order ?? "ID").ToList();

            return new PagedList<T>(lista, pageIndex, pageSize);
        }

        public static PagedList<T> GetPagedList<T>(String order, String where, int pageIndex, int pageSize)
        {
            return GetPagedList<T>(order, pageIndex, pageSize);
        }

        public static PagedList<T> GetPagedList<T>(String order, Expression<Func<T, Boolean>> where, int pageIndex, int pageSize)
        {
            order = order ?? "ID";
            if (!order.Contains("it."))
                order = "it." + order;
            List<T> lista = SelectAll<T>().OrderBy(order ?? "ID").Where(where.Compile()).ToList();

            return new PagedList<T>(lista, pageIndex, pageSize);
        }

        public static PagedList<T> GetPagedList<T>(String order, IQueryableObject<T> criteria, int pageIndex, int pageSize)
        {
            order = order ?? "ID";
            if (!order.Contains("it."))
                order = "it." + order;
            List<T> lista = SelectAll<T>().OrderBy(order ?? "ID").Where(criteria.GetPredicate()).ToList();

            return new PagedList<T>(lista, pageIndex, pageSize);
        }

        public static List<T> SelectAll<T>(Int32 maximumRows, Int32 startRowIndex)
        {
            return SelectAll<T>().Skip(startRowIndex).Take(maximumRows).ToList();
        }

        public static List<T> SelectWhere<T>(Expression<Func<T, Boolean>> where)
        {
            return SelectAll<T>().Where(where.Compile()).ToList();
        }

        public static List<T> SelectWhere<T>(Expression<Func<T, Boolean>> where, Int32 maximumRows, Int32 startRowIndex)
        {
            return SelectAll<T>(maximumRows, startRowIndex).Where(where.Compile()).ToList();
        }

        public static List<T> Search<T, E>(E criteria) where E : IQueryableObject<T>
        {
            var lista = SelectAll<T>();
            if (criteria != null)
                lista = lista.Where(criteria.GetPredicate()).ToList();
            return lista;
        }
    }
}
